useState 函数式更新
与 class 组件中的 setState 方法不同,useState 不会自动合并更新对象。你可以用函数式的 setState 结合展开运算符来达到合并更新对象的效果。
jsx
const [state, setState] = useState({});
setState((prevState) => {
// 也可以使用 Object.assign
return { ...prevState, ...updatedValues };
});引用类型 state 更新
经常 state 数据不是简单数据类型(值类型),而是数组、对象之类(引用类型).而 React 组件的更新机制对 state 只进行浅对比,也就是更新某个复杂类型数据时只要它的引用地址没变,那就不会重新渲染组件。
尤其当被更新的引用类型数据需要依赖之前的数据时,容易踩坑,不触发更新。更新复杂 state 的时候必须传给它一个全新的对象,而不是复制了它引用地址再修改的对象。
两种解决办法:
结合展开运算符返回一个新对象
jsxfunction nextValue(preValue) { let ipValue = Number.parseInt(newVal, 10); if (Number.isNaN(ipValue)) { ipValue = undefined; } if (ipValue < 0) { ipValue = 0; } if (ipValue > 255) { ipValue = 255; } preValue[index] = ipValue; //这里数据引用地址的值同样被修改 return [...preValue]; // 数据最外层的引用地址不同即可触发组件更新 } setValue((preValue) => nextValue(preValue));深拷贝对象,用全新的副本更新数据。
jsxfunction nextValue(preValue) { const newValue = _.cloneDeep(preValue); let ipValue = Number.parseInt(newVal, 10); if (Number.isNaN(ipValue)) { ipValue = undefined; } if (ipValue < 0) { ipValue = 0; } if (ipValue > 255) { ipValue = 255; } newValue[index] = ipValue; //这里数据引用地址的值同样被修改 return newValue; // 数据最外层的引用地址不同即可触发组件更新 } setValue((preValue) => nextValue(preValue));
## 纠错与补充
- React 只比较 `setState` 传入值的引用是否变化,即便对象内部字段不同也不会触发重新渲染;因此“修改完再 `setState(obj)`”这种模式一定失效。
- 深拷贝可以用 `structuredClone`、`JSON.parse(JSON.stringify(obj))` 或 `lodash.cloneDeep`,但最推荐的还是 `immer`:`setState(prev => produce(prev, draft => { draft[index] = ipValue }))`,性能和可维护性都更好。
- 如果 state 结构很复杂,考虑拆分为多个 `useState` 或使用 `useReducer`;后者的 reducer 始终接收上一份 state,可避免层层展开。